﻿using myDocumentClustering.SAM;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace myDocumentClustering.DocumentTermMatrix
{

    public class Vocabulary
    {
        public Dictionary<String, Int32> glossary = new Dictionary<string, int>(); //<word, wordColNumber>
        Dictionary<Int32, String> reverseGlossary = new Dictionary<int, string>();

        public Boolean Contains(String word)
        {
            return glossary.ContainsKey(word);
        }


        public void CreateInverseIndex()
        {
            foreach(var word in glossary.Keys)
            {
            }
        }

        public Int32 Count
        {
            get { return glossary.Count; }
        }

        public Int32 this[String word]
        {
            get { return glossary[word]; }
            set
            {
                glossary[word] = value;
                reverseGlossary[value] = word;
            }
        }

        public String this[Int32 wordColNumber]
        {
            get { return reverseGlossary[wordColNumber]; }
        }
    }

    /// <summary>
    /// <word_index, word_count> normalized or made probalistic.
    /// </summary>
    public class DTV
    {
        public Dictionary<Int32, float> vector = new Dictionary<int, float>();  //<wordIndex, wordCount>

        public Int32 docIndex;

        public float norm = 0.0f;

        public void AddWord(Int32 wordColNumber)
        {
            if (vector.ContainsKey(wordColNumber))
                vector[wordColNumber] += 1;
            else vector[wordColNumber] = 1;
        }

        /// <summary>
        /// get the wordCount/or probability/ or whatever
        /// </summary>
        /// <param name="wordColNumber"></param>
        /// <returns></returns>
        public float this[Int32 wordColNumber]
        {
            get { return vector[wordColNumber]; }
            set { vector[wordColNumber] = value; }
        }

        /// <summary>
        /// refine: should we normalize the vector?
        /// make sure the result is not a NaN.
        /// </summary>
        public void NormalizeIt()
        {
            norm = 0.0f;
            foreach (var word in vector)
            {
                norm = norm + word.Value * word.Value;
            }

            norm = (float)Math.Sqrt((double)norm);


            if(float.IsNaN(norm))
            {
                throw new NotFiniteNumberException();
            }
        }

        /// <summary>
        /// refine: or should we calculate the ratio/probability?
        /// </summary>
        public void MakeItProbalistic()
        {
            throw new NotImplementedException();
        }

        public static float CalculateVectorsSimilarity(DTV v1, DTV v2)
        {
            return GetCosineSimilarity(v1, v2);
        }

        private static float GetCosineSimilarity(DTV v1, DTV v2)
        {
            return (v1 * v2) / v1.norm / v2.norm;
        }

        /// <summary>
        /// DTV +
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static DTV operator +(DTV v1, DTV v2)
        {
            //foreach(var key in v1.vector.Keys)
            //{
            //    if (v2.vector.ContainsKey(key))
            //        v2.vector[key] += v1.vector[key];//BUG: this will change the v2, we need v1+v2, but v2 is NOT meant to be changed.
            //    else v2.vector[key] = v1.vector[key];
            //}
            //return v2;

            DTV sum = new DTV();
            foreach(var key in v1.vector.Keys)
            {
                float wordCountBoth=0;
                if (v2.vector.ContainsKey(key))
                    wordCountBoth = v1[key] + v2[key];
                else
                    wordCountBoth = v1[key];

                sum[key] = wordCountBoth;
            }

            foreach (var key in v2.vector.Keys)
            {
                if (!v1.vector.ContainsKey(key))
                    sum[key] = v2[key];
            }

            return sum;
        }

        public static DTV operator /(DTV v, Int32 divisor)
        {
            DTV result = new DTV();
            foreach(var key in v.vector.Keys)
            {
                result[key] = v[key] / divisor;  //I have changed wordCount to a float instead of int32.
            }

            return result;
        }

        /// <summary>
        /// DTV *: inner product
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static float operator *(DTV v1, DTV v2)
        {
            float sum = 0.0f;
            foreach (var word in v1.vector.Keys)//it is OK here for some of v2.vector.Keys are missed. Because it is multiplication.
            {
                if(v2.vector.ContainsKey(word))
                {
                    sum += (v1.vector[word] * v2.vector[word]);
                }
            }
            return sum;
        }
    }

    public class Documents
    {
        Dictionary<String, Int32> doc2Index = new Dictionary<string, int>();
        Dictionary<Int32, String> index2Doc = new Dictionary<int, string>();

        public Int32 Count
        {
            get { return doc2Index.Count; }
        }

        public List<String> DocNames
        {
            get { return doc2Index.Keys.ToList(); }
        }

        public List<Int32> DocIndexes
        {
            get { return index2Doc.Keys.ToList(); }
        }
        
        public Int32 this[String fileName]
        {
            get { return doc2Index[fileName]; }
            set
            {
                doc2Index[fileName] = value;
                index2Doc[value] = fileName;
            }
        }

        public String this[Int32 fileIndex]
        {
            get { return index2Doc[fileIndex]; }
        }
    }

    public class DTM
    {
        Dictionary<Int32, DTV> matrix = new Dictionary<int, DTV>();

        /// <summary>
        /// get the vector for a doc
        /// </summary>
        /// <param name="docIndex"></param>
        /// <returns></returns>
        public DTV this[Int32 docIndex]
        {
            get { return matrix[docIndex]; }
            set { matrix[docIndex] = value; }
        }
    }

    /// <summary>
    /// Result should be a list of clusters. No more, no less.
    /// </summary>
    public class DocClusteringResult
    {
        Dictionary<Int32, Int32> result = new Dictionary<int, int>();

        public Int32 this[Int32 docFileIndex]
        {
            get { return result[docFileIndex]; }
            set { result[docFileIndex] = value; }
        }
    }



}
